<?php

class Neurona {

    //Cantidad de entradas X de la neurona: Por defecto son 2
    private $cantEntradas; // no esta incluido el bias -> X0 = -1
    
    //Vector de pesos sinapticos: La dimension es = a $cantEntradas
    private $pesosW;
    
    //Tasa de aprendizaje de la neurona
    private $tasaAprendizaje;
    
    //Ultima salida procesada por el perceptron
    private $ultimaSalida;
    
    //string de la funcion de activacion. debe estar definida fuera de la clase
    private $nombreFuncActivacion;

    //Error local
    private $localerror;
    
    // Constructor --> Se le pasa un array con los datos.
    public function __construct($datos) {
        //Algunas validaciones.        
        if (!isInteger($datos['cantEntradas']))
            die('cantEntradas debe ser un valor entero');
        if (!is_numeric($datos['tasaAprendizaje']))
            die('tasaAprendizaje debe ingresarla y ser numerico');
        
        $this->cantEntradas = $datos['cantEntradas'];
        $this->tasaAprendizaje = (isset($datos['tasaAprendizaje'])) ? $datos['tasaAprendizaje'] : NULL;
        
        if (isset($datos['pesosW']) && is_array($datos['pesosW']) && count($datos['pesosW']) == ($this -> cantEntradas +1 ))
            $this->pesosW = $datos['pesosW'];
        else
           $this->pesosW = get_random_w($this -> cantEntradas +1 );
                   
        $this->ultimaSalida = (isset($datos['ultimaSalida'])) ? $datos['ultimaSalida'] : NULL;
        $this->nombreFuncActivacion = (isset($datos['nombreFuncActivacion'])) ? $datos['nombreFuncActivacion'] : 'signo';
        $this->localerror = 0;
}

    public function procesar($entradas) {
        $entradas = array_merge(array(-1), $entradas); // se agrega el valor x0 del umbral o bias
        return $this->ultimaSalida = $this->funcion_activacion(pdot($entradas, $this->pesosW));
    }

    public function funcion_activacion($x) {
        $nombre = $this->nombreFuncActivacion;
        return $nombre($x);
    }
    
    public function entrenar($entradas, $salida) { // aca $entradas es un vector
        $this->procesar($entradas);
        $entradas = array_merge(array(-1), $entradas); // se agrega el valor x0 del umbral o bias
        $cambios = false;
        if ($this -> ultimaSalida != $salida) {
            //echo 'SE MODIFICO <br /><br />';
            $n = count($this->pesosW);
            for ($i=0; $i <  $n ; $i++) {
                $this->pesosW[$i] += ($this->tasaAprendizaje)*($this->ultimaSalida - $salida) * $entradas[$i]; // actualizacion de los pesos
            }
            $cambios = true;
        }
        $this->localerror = ($salida - $this->ultimaSalida);
        return $cambios;
    }   
    
    /*
       entrenar_N  : Entrenamiento de n epocas.

       $entradas: matriz de patrones por fila
       $salidas: vector de salidas esperadas
       $epocas: es un entero que indica la cantidad de veces que se entrena sobre la matriz de entrada
       $error: es el error cuadratico medio de la epoca minimo con el cual se deja de entrenar
       $frecuenciaw : Cada cuantos entrenamientos, agrega un vector de W para despues poder ser graficado.
       *** NOTA: Cada fila de $entradas se corresponde con la misma fila de $salidas    
    */
    public function entrenar_N($entradas, $salidas, $epocas, $error, $frecuenciaw = 5) {
        $returnW = array(); //Devolvemos un historico de los pesosW
        $n = count($entradas);
        $returnW[] = $this->pesosW;
        for ($j = 0 ; $j < $epocas; $j++) {
            $errort = 0;
            for ($i = 0 ; $i < $n; $i++) {
                $cambios = $this->entrenar($entradas[$i], current($salidas[$i]) );
                // if (($i+$j) % $frecuenciaw == 0 && $cambios==true)
                //   $returnW[] = $this->pesosW;
                $errort += (current($salidas[$i]) - $this -> ultimaSalida)^2;
            if ($cambios)
                $returnW[] = $this->pesosW;
            }

            if ($errort/2 <= $error )
                return $returnW; //Salida del algoritmo x cumplirse condicion del error
        }
        $returnW[] = $this->pesosW;
        return $returnW; //Salida por cumplirse la cantidad de epocas a entrenar
    }
    
    public function prueba($entradas, $salidas) {
        $returnW = array();
        $n = count($entradas);
        for ($i = 0 ; $i < $n; $i++) {
            echo "salida: ".current($salidas[$i]) . " == Proceso: ".$this->procesar($entradas[$i]) ."<br />";
            if ( current($salidas[$i]) == $this->procesar($entradas[$i]))
                echo "<span style='color: green'>EAEA :)</span><br>";
            else
                echo "AEAE :(<br>";
        }
    }
    
};
